package org.dromara.common.web.interceptor;

import cn.hutool.core.io.IoUtil;
import cn.hutool.core.map.MapUtil;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.time.StopWatch;
import org.dromara.common.core.utils.SpringUtils;
import org.dromara.common.core.utils.StringUtils;
import org.dromara.common.json.utils.JsonUtils;
import org.dromara.common.web.filter.RepeatedlyRequestWrapper;
import org.springframework.http.MediaType;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;

import java.io.BufferedReader;
import java.util.Map;


/**
 * @Author 刘武贵
 * @Date 2024/8/15 22:00
 * @Description 功能描述：web的调用时间统计拦截器
 * dev环境有效
 */
/**
 * 实现HandlerInterceptor接口的类，用于在处理请求之前和之后执行拦截操作
 * 主要用于统计web服务方法的调用时间，以监控和优化性能
 */
@Slf4j
public class PlusWebInvokeTimeInterceptor implements HandlerInterceptor {

    /**
     * 定义一个常量，表示产品环境的配置标识
     */
    private final String prodProfile = "prod";

    /**
     * 使用ThreadLocal存储StopWatch对象，用于线程安全地监控和管理缓存访问时间
     */
    private final static ThreadLocal<StopWatch> KEY_CACHE = new ThreadLocal<>();

    /**
     * 在请求处理之前执行的拦截器方法
     * 该方法主要用于监控和记录请求的开始时间，并根据请求的类型记录请求的参数
     * 只在当前环境为生产环境时进行监控和记录
     *
     * @param request  HTTP请求对象，用于获取请求方法、请求URI和请求参数等信息
     * @param response HTTP响应对象，未在此方法中使用，但必须传递以满足方法签名要求
     * @param handler  处理请求的对象，未在此方法中使用，但必须传递以满足方法签名要求
     * @return 总是返回true，表示请求可以继续进行不会被此拦截器拦截或结束
     * @throws Exception 如果记录请求参数过程中发生错误，将抛出异常，但在此方法中未处理异常
     */
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        if (!prodProfile.equals(SpringUtils.getActiveProfile())) {
            // 构造请求的完整描述，包括请求方法和请求URI
            String url = request.getMethod() + " " + request.getRequestURI();

            // 打印请求参数
            // 如果是JSON请求，记录JSON格式的请求参数
            if (isJsonRequest(request)) {
                String jsonParam = "";
                // 检查是否为可重复读取的请求，以正确获取请求体中的JSON数据
                if (request instanceof RepeatedlyRequestWrapper) {
                    BufferedReader reader = request.getReader();
                    jsonParam = IoUtil.read(reader);
                }
                // 记录JSON请求的开始和请求参数
                log.info("[PLUS]开始请求 => URL[{}],参数类型[json],参数:[{}]", url, jsonParam);
            } else {
                // 如果不是JSON请求，记录传统的请求参数
                Map<String, String[]> parameterMap = request.getParameterMap();
                if (MapUtil.isNotEmpty(parameterMap)) {
                    // 将请求参数转换为JSON字符串并记录
                    String parameters = JsonUtils.toJsonString(parameterMap);
                    log.info("[PLUS]开始请求 => URL[{}],参数类型[param],参数:[{}]", url, parameters);
                } else {
                    // 如果没有请求参数，仅记录请求的开始
                    log.info("[PLUS]开始请求 => URL[{}],无参数", url);
                }
            }

            // 使用StopWatch开始记录请求处理的时间
            StopWatch stopWatch = new StopWatch();
            KEY_CACHE.set(stopWatch);
            stopWatch.start();
        }
        // 表示请求可以继续，不会被当前拦截器拦截
        return true;
    }

    /**
     * 重写postHandle方法，用于在控制器方法执行完成后进行后处理
     * 本方法可以用于全局异常处理、添加公共数据、统一API格式化等跨切面操作
     *
     * @param request     当前的HTTP请求对象
     * @param response    当前的HTTP响应对象
     * @param handler     执行的控制器方法的对象
     * @param modelAndView    执行控制器方法后的ModelAndView对象
     * @throws Exception   处理过程中可能抛出的异常
     */
    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {

    }

    /**
     * 在请求完成之后执行的动作
     * 此方法用于在非生产环境下记录请求的耗时信息
     *
     * @param request  HTTP请求对象，用于获取请求方法和请求URL
     * @param response HTTP响应对象，未在此方法中使用
     * @param handler  请求处理对象，未在此方法中使用
     * @param ex       异常对象，如果请求过程中发生了未捕获的异常，则此参数为该异常
     * @throws Exception 如果在日志记录过程中发生错误，可能会抛出异常
     */
    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
        // 只有在当前环境不是生产环境时，才执行以下操作
        if (!prodProfile.equals(SpringUtils.getActiveProfile())) {
            // 从线程本地存储中获取StopWatch对象
            StopWatch stopWatch = KEY_CACHE.get();
            // 停止计时
            stopWatch.stop();
            // 记录日志，输出请求方法、请求URL和请求的耗时
            log.info("[PLUS]结束请求 => URL[{}],耗时:[{}]毫秒", request.getMethod() + " " + request.getRequestURI(), stopWatch.getTime());
            // 从线程本地存储中移除StopWatch对象
            KEY_CACHE.remove();
        }
    }

    /**
     * 判断本次请求的数据类型是否为json
     *
     * @param request request HTTP请求对象，用于获取请求的内容类型
     * @return boolean 如果内容类型是JSON，则返回true；否则返回false
     */
    private boolean isJsonRequest(HttpServletRequest request) {
        // 获取请求的内容类型
        String contentType = request.getContentType();
        if (contentType != null) {
            // 判断内容类型是否为JSON
            return StringUtils.startsWithIgnoreCase(contentType, MediaType.APPLICATION_JSON_VALUE);
        }
        return false;
    }

}
